package com.ecp.commons.dao;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.JSONObject;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ecp.commons.bean.IDS;
import com.ecp.commons.db.GenericDAOImpl;

/*
 * 如果取ids在一个事务里，取出了一个nextValue，并更新数据库（加上一个步长）。
 * 但是事务回滚了。数据库中的nextValue还原了。
 * 当当前步长用完了，再取ids的时候，就会取得原来的nextValue。
 * 
 * 避免：
 * 1. ids不能放到事务中，必须单独线程（如何知道该取了？）
 * 2. 如果发现取得nextValue比现在的还小，纠正。        
 * 
 * */
@Repository("idsDAO")
public class IDSDAOImpl extends GenericDAOImpl<IDS> implements IDSDAO {
	private static final int	STEP	= 100;							// 一次预取n个，减少数据库的读写次数
	private Map<String, IDS>	mapIds	= new HashMap<String, IDS>();

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	// 防止事务回滚
	public synchronized int generateId(final String tableName) throws Exception {
		IDS ids = mapIds.get(tableName);
		if ((ids == null) || (ids.getCurValue() >= ids.getNextValue() + STEP)) {
			int curValue = (ids == null) ? 0 : ids.getCurValue();
			try {
				ids = this.getIds(tableName, curValue);
				if (!validateIds(tableName, ids)) {
					throw new Exception("nextValue is invalid.");
				}
			} catch (Exception e) {
				// 只有多服务器同数据库才会出现问题，再来一次，两次冲突的可能性很小了
				try {
					ids = this.getIds(tableName, curValue);
				} catch (Exception e1) {
					throw e1;
				}
			}
			ids.setCurValue(ids.getNextValue());
			mapIds.put(tableName, ids);
		}
		// 如果出错已经抛出异常了，所以到这里肯定是正常情况
		int newId = ids.getCurValue();
		ids.setCurValue(newId + 1);
		return newId;
	}

	// 可以不需要做成事务，因为UPDATA带有校验了
	private synchronized IDS getIds(final String tableName, int curValue) throws Exception {
		IDS ids = this.load(tableName, new IDS());
		if (ids != null) {
			int step = STEP;
			int nextValue = ids.getNextValue();
			if (curValue > nextValue) {
				// 数据库出现了问题，估计是回滚导致的。直接补充n个步长使数据库中的nextValue够大
				// TODO: 这部分实现逻辑待优化
				step = ((curValue + 1 - nextValue) / STEP + 1) * STEP;
				nextValue = curValue + 1;
			}
			String update = "UPDATE t_ids SET nextValue=nextValue + " + step + " WHERE tableName=? AND nextValue=?;";
			int res = this.execute(update, new Object[] { tableName, ids.getNextValue() }, new IDS());
			if (res != 1) {
				throw new SQLException("Generate id failed.");
			}
			ids.setNextValue(nextValue);
		}
		return ids;
	}

	private boolean validateIds(final String tableName, IDS ids) {
		if (tableName.equals("t_column"))
			return true;
		boolean res = true;
		String sql = "SELECT id FROM " + tableName + " ORDER by id DESC LIMIT 1";
		try {
			List<JSONObject> list = this.search(sql, new Object[] {}, null);
			if (list.size() > 0) {
				long id = list.get(0).getLong("id");
				if (id >= ids.getNextValue()) {
					res = false;
					String update = "UPDATE t_ids SET nextValue=" + (id + 1) + " WHERE tableName=?";
					this.execute(update, new Object[] { tableName }, new IDS());
				}
			}
		} catch (Exception e) {
			//e.printStackTrace();
		}
		return res;
	}

	public synchronized boolean clearIds(final String tableName) {
		return (mapIds.remove(tableName) != null);
	}
}